extern crate bindgen;
use std::env;
use std::path::{Path, PathBuf};

static SPDK_LIB_PATH: &str = "spdk/include";

struct Generator<'a> {
    spdk_include_path: &'a Path,
    out_path: &'a Path,
}
impl<'a> Generator<'a> {
    fn generate(&self, names: &[&str]) {
        let project_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
        println!("cargo:warning=project root:{}", project_dir.display());
        let mut codegen_config = bindgen::CodegenConfig::empty();
        codegen_config.set(bindgen::CodegenConfig::FUNCTIONS, true);
        codegen_config.set(bindgen::CodegenConfig::TYPES, true);

        let mut builder = bindgen::builder();
        for name in names {
            let header_path = self.spdk_include_path.join(
                PathBuf::from("spdk/header.h")
                    .with_file_name(name)
                    .with_extension("h"),
            );
            println!(
                "cargo:warning=header path: {}",
                project_dir.join(&header_path).to_string_lossy()
            );
            builder = builder.header(format!("{}", project_dir.join(header_path).display()));
        }

        let bindings = builder
            .derive_default(true)
            .with_codegen_config(codegen_config)
            .generate_inline_functions(false)
            .trust_clang_mangling(false)
            .rustfmt_configuration_file(Some(PathBuf::from(".rustfmt.toml")))
            .layout_tests(false)
            .ctypes_prefix("libc")
            .blocklist_item("IPPORT_.*")
            .blocklist_type("spdk_nvme_tcp_rsp")
            .blocklist_type("spdk_nvme_tcp_cmd")
            .blocklist_type("spdk_nvmf_fabric_prop_get_rsp")
            .blocklist_type("spdk_nvmf_fabric_connect_rsp")
            .blocklist_type("spdk_nvmf_fabric_connect_cmd")
            .blocklist_type("spdk_nvmf_fabric_auth_send_cmd")
            .blocklist_type("spdk_nvmf_fabric_auth_recv_cmd")
            .blocklist_type("spdk_nvme_health_information_page")
            .blocklist_type("spdk_nvme_ctrlr_data")
            .blocklist_function("spdk_nvme_ctrlr_get_data")
            .opaque_type("spdk_blob_ext_io_opts")
            .opaque_type("spdk_bdev_ext_io_opts")
            .opaque_type("spdk_nvme_sgl_descriptor")
            .generate()
            .expect("Unable to generate bindings");

        bindings
            .write_to_file(self.out_path.join("bindings.rs"))
            .expect("Couldn't write bindings!");
    }
}

fn gen_bindings() {
    let spdk_include_path = PathBuf::from(SPDK_LIB_PATH);
    let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
    let gen = Generator {
        spdk_include_path: Path::new(&spdk_include_path),
        out_path: Path::new(&out_path),
    };

    let headers = [
        "nvme",
        "event",
        "blobfs",
        "env",
        "bdev",
        "blobfs_bdev",
        "blob_bdev",
        "blob",
        "log",
    ];

    gen.generate(&headers);
}

fn main() {
    gen_bindings();
    let project_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
    println!("cargo:return-if-changed=build.rs");
    println!("cargo:rustc-link-lib=spdk");
    println!(
        "cargo:rustc-link-search=native={}",
        project_dir.join("spdk/include").display()
    );
}
